/**
 * 执行对画布的各种操作
 * @Author: zhangshubin
 * @Date: 2023-12-06 10:34:52
 * @Last Modified by: zhangshubin
 * @Last Modified time: 2023-12-26 18:17:12
 */

// import mx from '@/plugins'

import Editor from '../core/Editor'
import { Graph } from '../types'
import * as mx from 'mxgraph'

const AUTO_SAVE_XML_KEY = 'autosaveXml'
const AUTO_SAVE_FILE_META_KEY = 'autosaveFileMeta'
export default class EditorExecutor {
  static graph: Graph
  static editor: Editor
  static fileMeta: HFileMeta | undefined

  static setEditor(editor: Editor) {
    this.editor = editor
    this.graph = editor.graph as Graph
  }

  /**
   * 是否切换为页面模式
   * @param value
   */
  static setPageVisible(value: boolean) {
    const hasScrollbars: boolean = mxUtils.hasScrollbars(this.graph.container)
    let tx = 0
    let ty = 0

    if (hasScrollbars) {
      tx = this.graph.view.translate.x * this.graph.view.scale - this.graph.container.scrollLeft
      ty = this.graph.view.translate.y * this.graph.view.scale - this.graph.container.scrollTop
    }

    this.graph.pageVisible = value
    this.graph.pageBreaksVisible = value
    this.graph.preferPageSize = value
    this.graph.view.validateBackground()

    // Workaround for possible handle offset
    if (hasScrollbars) {
      const cells = this.graph.getSelectionCells()
      this.graph.clearSelection()
      this.graph.setSelectionCells(cells)
    }

    if (hasScrollbars) {
      const pages = this.graph.getPageLayout()
      const pad = this.graph.getPagePadding()
      const size = this.graph.getPageSize()

      // Updates the minimum graph size
      const minw = Math.ceil(2 * pad.x + pages.width * size.width)
      const minh = Math.ceil(2 * pad.y + pages.height * size.height)

      const min = this.graph.minimumGraphSize

      // LATER: Fix flicker of scrollbar size in IE quirks mode
      // after delayed call in window.resize event handler
      if (min == null || min.width != minw || min.height != minh) {
        this.graph.minimumGraphSize = new mxRectangle(0, 0, minw, minh)
      }

      // Updates auto-translate to include padding and graph size
      const dx = pad.x - pages.x * size.width
      const dy = pad.y - pages.y * size.height

      if (
        !this.graph.autoTranslate &&
        (this.graph.view.translate.x != dx || this.graph.view.translate.y != dy)
      ) {
        this.graph.autoTranslate = true
        //@ts-ignore
        this.graph.view.x0 = pages.x
        //@ts-ignore
        this.graph.view.y0 = pages.y

        // NOTE: THIS INVOKES THIS METHOD AGAIN. UNFORTUNATELY THERE IS NO WAY AROUND THIS SINCE THE
        // BOUNDS ARE KNOWN AFTER THE VALIDATION AND SETTING THE TRANSLATE TRIGGERS A REVALIDATION.
        // SHOULD MOVE TRANSLATE/SCALE TO VIEW.
        const tx1 = this.graph.view.translate.x
        const ty1 = this.graph.view.translate.y
        this.graph.view.setTranslate(dx, dy)

        // LATER: Fix rounding errors for small zoom
        this.graph.container.scrollLeft += Math.round((dx - tx1) * this.graph.view.scale)
        this.graph.container.scrollTop += Math.round((dy - ty1) * this.graph.view.scale)

        this.graph.autoTranslate = false

        return
      }
      // Calls updatePageBreaks
      this.graph.sizeDidChange()

      if (hasScrollbars) {
        this.graph.container.scrollLeft = this.graph.view.translate.x * this.graph.view.scale - tx
        this.graph.container.scrollTop = this.graph.view.translate.y * this.graph.view.scale - ty
      }
    }
  }

  /**
   * 设置新的页面尺寸
   */
  static setPageFormat(value: mx.mxRectangle) {
    this.graph.pageFormat = value
    this.graph.view.validateBackground()
    this.graph.sizeDidChange()
    // this.editor.resetScrollbars()
  }

  /**
   * 打开文件
   * @param file
   */
  static open(file: HFile) {
    this.fileMeta = file.meta
    if (file.meta?.name) {
      window.document.title = file.meta?.name
    }
    this.openXmlString(file.content)
  }

  /**
   * 保存
   */
  static saveLocal() {
    const xml = this.editor.getGraphXml()
    window.localStorage.setItem(AUTO_SAVE_XML_KEY, xml)
    window.localStorage.setItem(AUTO_SAVE_FILE_META_KEY, JSON.stringify(this.fileMeta))
  }

  /**
   * 读取缓存中的文件
   * @returns
   */
  static renderGraphFromCache() {
    const xml = window.localStorage.getItem(AUTO_SAVE_XML_KEY) || ''
    const metaStr = window.localStorage.getItem(AUTO_SAVE_FILE_META_KEY) || ''
    try {
      let meta = { name: '新文件' }
      if (metaStr) {
        meta = JSON.parse(metaStr)
      }
      this.open({
        meta,
        content: xml
      })
      this.openXmlString(xml)
    } catch (error) {
      console.error('renderGraphFromCache err')
    }
  }

  /**
   * 打开一个字符形式的
   * @param xml
   * @returns
   */
  static openXmlString(xml: string | null) {
    if (!xml) {
      xml = '<root><mxCell id="0"/><mxCell id="1" parent="0"/></root>'
    }

    const xmlDocument = mxUtils.parseXml(xml) //eslint-disable-line
    const node = xmlDocument.documentElement
    this.editor.setGraphXml(node)

    this.graph.view.validate()
    this.graph.sizeDidChange()
    this.editor.resetScrollbars()
  }

  /**
   * 对齐网格
   */
  static alignCells(align: string, cells?: mx.mxCell[]) {
    //@ts-ignore
    this.graph.alignCells(align, cells)
  }

  /**
   * 均匀分布
   * @param isHorizontal
   */
  static distributeCells(isHorizontal: boolean, cells?: mx.mxCell[]) {
    this.graph.distributeCells(isHorizontal, cells)
  }

  /**
   * toggle样式风格
   * @param style
   */
  static toggleCellStyles(style: string) {
    this.graph.toggleCellStyles(style, false)
  }
  /**
   * 调整透明度
   */
  static setOpacity(opacity: number, cells?: mx.mxCell[]) {
    this.graph.setCellStyles('opacity', opacity, cells)
  }
  /**
   * 调整填充颜色
   */
  static setFillColor(color: string, cells?: mx.mxCell[]) {
    this.graph.setCellStyles('fillColor', color, cells)
  }

  /**
   * 设置Cell样式
   */
  static setCellStyles(key: string, value: string | number | null, cells?: mx.mxCell[]) {
    this.graph.setCellStyles(key, value, cells)
  }

  /**
   * 设置样式符号
   * @param key
   * @param flag
   * @param cells
   */
  static toggleCellStyleFlags(key: string, flag: number, cells?: mx.mxCell[]) {
    this.graph.toggleCellStyleFlags(key, flag, cells)
  }

  /**
   * 更新Cell的几何信息
   * @param key
   * @param value
   * @param cells
   */
  static updateCellGeometry(
    key: 'width' | 'height' | 'x' | 'y',
    value: number,
    cells?: mx.mxCell[]
  ) {
    cells = cells || this.graph.getSelectionCells()
    this.graph.getModel().beginUpdate()
    try {
      for (let i = 0; i < cells.length; i++) {
        if (this.graph.getModel().isVertex(cells[i])) {
          let geo = this.graph.getCellGeometry(cells[i])

          if (geo != null) {
            geo = geo.clone()
            geo[key] = value

            const state = this.graph.view.getState(cells[i])

            if (state != null && this.graph.isRecursiveVertexResize(state)) {
              this.graph.resizeChildCells(cells[i], geo)
            }

            this.graph.getModel().setGeometry(cells[i], geo)
            this.graph.constrainChildCells(cells[i])
          }
        }
      }
    } finally {
      this.graph.getModel().endUpdate()
    }
  }
  /**
   * 缩放
   * @param v
   */
  static zoom(v: number) {
    this.graph.zoomTo(v)
  }
  /**
   * 修改背景色
   * @param value
   */
  static setBackgroundColor(value: string) {
    this.graph.background = value
    this.graph.view.validateBackground()
  }

  /**
   * 设置网格背景是否可见
   * @param v
   */
  static setGridEnabled(v: boolean) {
    this.graph.gridEnabled = v
    this.graph.view.validateBackground()
  }

  /**
   * 设置网格间距
   */
  static setGridSize(size: number) {
    this.graph.gridSize = size
    this.graph.view.validateBackground()
  }

  /**
   * 设置属性值
   * @param attrName 为
   * @param attrValue
   * @param cell
   */
  static setAttrValue(attrName: string, attrValue: string, cells?: mx.mxCell[]) {
    if (!cells) {
      cells = this.graph.getSelectionCells()
    }
    if (!cells) {
      return
    }
    this.graph.model.beginUpdate()
    try {
      cells.forEach((cell) => {
        const value: any = this.graph.model.getValue(cell)

        if (mxUtils.isNode(value, 'object')) {
          const value2: Element = value.cloneNode(true)
          value2.setAttribute(attrName, attrValue)
          cell.setValue(value2)
        }
      })
    } catch (error) {
      console.error('setAttrValue err', error)
    }
    this.graph.model.endUpdate()
  }

  /**
   * 组合多个图元
   */
  static groupCells() {
    const graph = this.graph
    if (graph.isEnabled()) {
      let cells = mxUtils.sortCells(graph.getSelectionCells(), true)

      if (cells.length == 1 && !graph.isTable(cells[0]) && !graph.isTableRow(cells[0])) {
        graph.setCellStyles('container', '1')
      } else {
        cells = graph.getCellsForGroup(cells)

        if (cells.length > 1) {
          graph.setSelectionCell(graph.groupCells(null, 0, cells))
        }
      }
    }
  }

  /**
   * 取消群组
   */
  static ungroupCells() {
    const graph = this.graph
    let temp
    if (graph.isEnabled()) {
      const cells = graph.getSelectionCells()

      graph.model.beginUpdate()
      try {
        //@ts-ignore
        temp = graph.ungroupCells()

        // Clears container flag for remaining cells
        if (cells != null) {
          for (let i = 0; i < cells.length; i++) {
            if (graph.model.contains(cells[i])) {
              if (graph.model.getChildCount(cells[i]) == 0 && graph.model.isVertex(cells[i])) {
                graph.setCellStyles('container', '0', [cells[i]])
              }

              temp.push(cells[i])
            }
          }
        }
      } finally {
        graph.model.endUpdate()
      }

      graph.setSelectionCells(temp)
    }
  }

  /**
   * 撤销
   */
  static undo() {
    try {
      const graph = this.graph

      if (graph.isEditing()) {
        // Stops editing and executes undo on graph if native undo
        // does not affect current editing value
        const value = graph.cellEditor.textarea.innerHTML
        document.execCommand('undo', false, undefined)

        if (value == graph.cellEditor.textarea.innerHTML) {
          graph.stopEditing(true)
          this.editor.undoManager.undo()
        }
      } else {
        this.editor.undoManager.undo()
      }
    } catch (e) {
      // ignore all errors
    }
  }

  /**
   * 重做
   */
  static redo() {
    try {
      const graph = this.graph

      if (graph.isEditing()) {
        document.execCommand('redo', false, undefined)
      } else {
        this.editor.undoManager.redo()
      }
    } catch (e) {
      // ignore all errors
    }
  }

  /**
   * 设置是否可连接
   */
  static setConnectable(flag: boolean) {
    this.graph.setConnectable(flag)
  }

  /**
   * 设置连接箭头是否可用
   */
  static setConnectArrowEnabled(flag: boolean) {
    this.graph.connectionArrowsEnabled = flag
    // this.graph.view.validate()
  }

  /**
   * 插入图片
   */
  static updateImage(url: string, height?: number, width?: number) {
    const cells = this.graph.getSelectionCells()
    const model = this.graph.model
    model.beginUpdate()
    try {
      for (let i = 0; i < cells.length; i++) {
        const cell = cells[i]
        if (this.graph.getModel().isVertex(cell)) {
          //@ts-ignore
          const style = mxUtils.setStyle(model.getStyle(cell), 'image', url)
          model.setStyle(cell, style)
          let geo = this.graph.getCellGeometry(cells[i])

          if (geo != null) {
            geo = geo.clone()
            if (height) {
              geo.height = height
            }
            if (width) {
              geo.width = width
            }

            const state = this.graph.view.getState(cells[i])

            if (state != null && this.graph.isRecursiveVertexResize(state)) {
              this.graph.resizeChildCells(cells[i], geo)
            }

            this.graph.getModel().setGeometry(cells[i], geo)
            this.graph.constrainChildCells(cells[i])
          }
        }
      }
    } finally {
      model.endUpdate()
    }
  }
}
